#include "input.h"
#include "state.h"
#include "config.h"
#include "utils.h"
#include "draw.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <X11/Xatom.h>
#include <mutex>
static bool isItemVisible(Item *item);
static Item *getLastPageFirstItem();

// Grab keyboard focus for the menu window
void grabFocus()
{
    State &state = getState();
    if (!state.dpy || !state.win)
        return;
    // Retry focus grab (up to 100 times) to handle race conditions
    for (int i = 0; i < 100; ++i)
    {
        XSetInputFocus(state.dpy, state.win, RevertToParent, CurrentTime);
        Window focusWin;
        int revert;
        XGetInputFocus(state.dpy, &focusWin, &revert);
        if (focusWin == state.win)
            break;
        usleep(10000); // 10ms delay between retries
    }
}

// Grab global keyboard (prevents other windows from receiving input)
void grabKeyboard()
{
    State &state = getState();
    if (!state.dpy || !state.win || state.cfg.embed_window)
        return;
    // Retry keyboard grab (up to 1000 times)
    for (int i = 0; i < 1000; ++i)
    {
        if (XGrabKeyboard(state.dpy, state.win, True, GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess)
        {
            break;
        }
        usleep(1000); // 1ms delay between retries
    }
}

// Handle clipboard paste (called after selection notify)
void pasteFromSelection()
{
    State &state = getState();
    if (!state.dpy || !state.win)
        return;

    char *data = nullptr;
    Atom actualType;
    int actualFormat;
    unsigned long nItems, bytesAfter;

    // Get clipboard data from window property
    int result = XGetWindowProperty(
        state.dpy, state.win, state.utf8,
        0, BUFSIZ / 4 + 1, False, state.utf8,
        &actualType, &actualFormat, &nItems, &bytesAfter,
        (unsigned char **)&data);

    if (result == Success && data)
    {
        // Insert pasted text into input
        state.text.insert(state.cursor, data);
        state.cursor += strlen(data);
        filterMatches(); // Update filtered items
        XFree(data);
    }

    // Redraw menu with new input
    if (state.drw)
        state.drw->drawMenu();
}

// Handle key press events
void handleKeyPress(const XKeyEvent *ev)
{
    State &state = getState();
    if (!state.dpy || !state.xic)
        return;

    char buf[64] = {0};
    KeySym keySym = NoSymbol;
    Status status;
    // Convert key event to Unicode text (supports IME)
    int len = XmbLookupString(state.xic, (XKeyPressedEvent *)ev, buf, sizeof(buf) - 1, &keySym, &status);

    // Handle printable characters (insert into input)
    if (len > 0 && !iscntrl((unsigned char)buf[0]))
    {
        state.text.insert(state.cursor, buf);
        state.cursor += len;
        filterMatches();
        state.drw->drawMenu();
        return;
    }

    // Handle modifier + key combinations (shortcuts)
    uint32_t mod = ev->state & (ShiftMask | ControlMask | Mod1Mask | Mod4Mask);
    uint32_t shortcutKey = (mod << 16) | keySym;
    auto it = state.cfg.shortcuts.find(shortcutKey);
    if (it == state.cfg.shortcuts.end())
        return;

    switch (it->second)
    {
    case ShortcutAction::CURSOR_HOME:
        state.cursor = 0;
        break;
    case ShortcutAction::CURSOR_END:
        state.cursor = state.text.size();
        break;
    case ShortcutAction::CURSOR_LEFT:
        if (state.cursor > 0)
        {
            state.cursor = nextrune(state.cursor, -1); // Use UTF-8 aware cursor move
        }
        break;
    case ShortcutAction::CURSOR_RIGHT:
        if (
            state.cursor < state.text.size())
        {
            state.cursor = nextrune(state.cursor, 1); // Use UTF-8 aware cursor move
        }
        break;
    case ShortcutAction::CURSOR_WORD_LEFT:
    {
        // Move cursor to start of previous word
        while (state.cursor > 0 && strchr(state.cfg.word_delimiters.c_str(), state.text[state.cursor - 1]))
        {
            state.cursor = nextrune(state.cursor, -1);
        }
        while (state.cursor > 0 && !strchr(state.cfg.word_delimiters.c_str(), state.text[state.cursor - 1]))
        {
            state.cursor = nextrune(state.cursor, -1);
        }
        break;
    }
    case ShortcutAction::CURSOR_WORD_RIGHT:
    {
        // Move cursor to end of next word
        while (state.cursor < state.text.size() && !strchr(state.cfg.word_delimiters.c_str(), state.text[state.cursor]))
        {
            state.cursor = nextrune(state.cursor, 1);
        }
        while (state.cursor < state.text.size() && strchr(state.cfg.word_delimiters.c_str(), state.text[state.cursor]))
        {
            state.cursor = nextrune(state.cursor, 1);
        }
        break;
    }
    case ShortcutAction::DELETE_LEFT:
        if (state.cursor > 0)
        {
            size_t prevCursor = nextrune(state.cursor, -1);
            state.text.erase(prevCursor, state.cursor - prevCursor);
            state.cursor = prevCursor;
            filterMatches();
        }
        break;
    case ShortcutAction::DELETE_RIGHT:
        if (state.cursor < state.text.size())
        {
            size_t nextCursor = nextrune(state.cursor, 1);
            state.text.erase(state.cursor, nextCursor - state.cursor);
            filterMatches();
        }
        break;
    case ShortcutAction::DELETE_WORD:
    {
        // Delete previous word
        size_t start = state.cursor;
        while (start > 0 && strchr(state.cfg.word_delimiters.c_str(), state.text[start - 1]))
        {
            start = nextrune(start, -1);
        }
        while (start > 0 && !strchr(state.cfg.word_delimiters.c_str(), state.text[start - 1]))
        {
            start = nextrune(start, -1);
        }
        state.text.erase(start, state.cursor - start);
        state.cursor = start;
        filterMatches();
        break;
    }
    case ShortcutAction::DELETE_ALL:
        state.text.clear();
        state.cursor = 0;
        filterMatches();
        break;
    case ShortcutAction::COMPLETE:
        // Auto-complete with selected item
        if (state.sel)
        {
            state.text = state.sel->text;
            state.cursor = state.text.size();
            filterMatches();
        }
        break;
    case ShortcutAction::SELECT_UP:
        // Select previous item (wrap to last if needed)
        if (state.sel)
        {
            state.sel = state.sel->left ? state.sel->left : state.matchend;
            // Scroll to previous page if selection is out of view
            if (!isItemVisible(state.sel))
            {
                state.curr = state.sel;
                calcOffsets();
            }
        }
        break;
    case ShortcutAction::SELECT_DOWN:
        // Select next item (wrap to first if needed)
        if (state.sel)
        {
            state.sel = state.sel->right ? state.sel->right : state.matches;
            // Scroll to next page if selection is out of view
            if (!isItemVisible(state.sel))
            {
                state.curr = state.sel;
                calcOffsets();
            }
        }
        break;
    case ShortcutAction::SELECT_FIRST:
        state.sel = state.matches;
        state.curr = state.matches;
        calcOffsets();
        break;
    case ShortcutAction::SELECT_LAST:
        state.sel = state.matchend;
        // Scroll to last page to show selected item
        state.curr = getLastPageFirstItem();
        calcOffsets();
        break;
    case ShortcutAction::PAGE_UP:
        // Scroll to previous page of items
        if (state.prev)
        {
            state.curr = state.prev;
            state.sel = state.curr;
            calcOffsets();
        }
        break;
    case ShortcutAction::PAGE_DOWN:
        // Scroll to next page of items
        if (state.next)
        {
            state.curr = state.next;
            state.sel = state.curr;
            calcOffsets();
        }
        break;
    case ShortcutAction::PASTE_PRIMARY:
        // Request primary selection (middle-click paste)
        XConvertSelection(state.dpy, XA_PRIMARY, state.utf8, state.utf8, state.win, CurrentTime);
        return;
    case ShortcutAction::PASTE_CLIPBOARD:
        // Request clipboard selection (Ctrl+V paste)
        XConvertSelection(state.dpy, state.clip, state.utf8, state.utf8, state.win, CurrentTime);
        return;
    case ShortcutAction::EXIT:
        cleanup();
        exit(EXIT_SUCCESS);
    case ShortcutAction::SUBMIT:
        // Print selected item or input text and exit
        if (state.sel && !(ev->state & ControlMask))
        {
            puts(state.sel->text.c_str());
        }
        else
        {
            puts(state.text.c_str());
        }
        cleanup();
        exit(EXIT_SUCCESS);
    default:
        return;
    }

    // Redraw menu after shortcut action
    if (state.drw)
        state.drw->drawMenu();
}

// Check if an item is in the currently visible page
static bool isItemVisible(Item *item)
{
    State &state = getState();
    if (!item || !state.curr)
        return false;
    Item *currItem = state.curr;
    for (int i = 0; i < state.cfg.lines; ++i)
    {
        if (currItem == item)
            return true;
        if (!currItem->right)
            break;
        currItem = currItem->right;
    }
    return false;
}

// Get the first item of the last page
static Item *getLastPageFirstItem()
{
    State &state = getState();
    if (!state.matchend)
        return nullptr;
    Item *item = state.matchend;
    for (int i = 1; i < state.cfg.lines; ++i)
    {
        if (!item->left)
            break;
        item = item->left;
    }
    return item;
}

// Handle mouse button press events
void handleButtonPress(const XButtonEvent *ev)
{
    State &state = getState();
    if (!state.dpy || !state.win)
        return;

    switch (ev->button)
    {
    case 4: // Scroll up (select previous item)
        if (state.sel)
        {
            state.sel = state.sel->left ? state.sel->left : state.matchend;
            if (!isItemVisible(state.sel))
            {
                state.curr = state.sel;
                calcOffsets();
            }
            state.drw->drawMenu();
        }
        break;
    case 5: // Scroll down (select next item)
        if (state.sel)
        {
            state.sel = state.sel->right ? state.sel->right : state.matches;
            if (!isItemVisible(state.sel))
            {
                state.curr = state.sel;
                calcOffsets();
            }
            state.drw->drawMenu();
        }
        break;
    case 1: // Left click (submit selected item)
        if (state.sel)
        {
            puts(state.sel->text.c_str());
            cleanup();
            exit(EXIT_SUCCESS);
        }
        break;
    default:
        break;
    }
}

// Main X11 event loop
void runEventLoop()
{
    State &state = getState();
    if (!state.dpy)
        return;

    XEvent ev;
    while (true)
    {
        XNextEvent(state.dpy, &ev);

        // Skip events not targeting the menu window
        if (XFilterEvent(&ev, state.win))
            continue;

        switch (ev.type)
        {
        case DestroyNotify:
            // Exit if menu window is destroyed
            if (ev.xdestroywindow.window == state.win)
            {
                cleanup();
                exit(EXIT_SUCCESS);
            }
            break;
        case Expose:
            // Redraw menu on expose (e.g., window unobscured)
            if (ev.xexpose.count == 0 && state.drw)
            {
                state.drw->drawMenu();
            }
            break;
        case FocusIn:
            // Grab focus when menu window receives focus
            if (ev.xfocus.window == state.win)
            {
                grabFocus();
            }
            break;
        case KeyPress:
            // Handle key presses
            handleKeyPress(&ev.xkey);
            break;
        case SelectionNotify:
            // Handle clipboard paste after selection is ready
            if (ev.xselection.property == state.utf8)
            {
                pasteFromSelection();
            }
            break;
        case VisibilityNotify:
            // Raise window if it becomes visible
            if (ev.xvisibility.state != VisibilityUnobscured)
            {
                XRaiseWindow(state.dpy, state.win);
            }
            break;
        case ButtonPress:
            // Handle mouse clicks
            handleButtonPress(&ev.xbutton);
            break;
        default:
            break;
        }
    }
}

// Proper implementation of the global state singleton
State &getState()
{
    static State instance;          // Single instance (lazy-initialized)
    static std::once_flag initFlag; // Ensure thread-safe initialization

    // Initialize the state once (if needed)
    std::call_once(initFlag, []()
                   {
                       // Optional: Add any initialization code here
                   });

    return instance;
}